#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<ctype.h>
#include<string.h>
#include<assert.h>
//第15讲：深⼊理解指针(5)+第16讲：字符函数和字符串函数
//3.4 题⽬4
//假设环境是x86环境，程序输出的结果是啥？
//int main()
//{
//	int a[5][5];
//	int(*p)[4];//p是一个数组指针，指向的数组是4个整型元素的 +1 就跳过4个int的大小
//	p = a;//类型有差异，a==&a[0]类型为:int(*)[5],但是p依然保持原来的类型不变,p现在指向a的首地址,后面可以用画图来理解.
//	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//指针-指针的绝对值 --> 两个指针之间的元素个数
//	return 0;
//	//&p[4][2] --> &*(*(p+4)+2) --> *(p+4)+2 --> p跳过4个int[4]大小，也就是4*16个字节大小，然后解引用
//	 //对数组指针解引用得到当前行的首元素地址
//	//例子:int(*p)[5]=&a[0] --> 这时的p相当于一个数组指针
//	//对p进行解引用相当于对&a[0]解引用 --> *&a[0]==a[0]
//	//又因为数组名是数组首元素的地址，所以对一个函数指针进行解引用得到的还是地址！！表示当前行的首元素地址，类型是int*
//}


//3.5 题⽬5
//int main()
//{
//	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//	int* ptr1 = (int*)(&aa + 1);
//	int* ptr2 = (int*)(*(aa + 1));
//	int(*p)[2][5] = &aa;
//	printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1));//依旧考察指针+-整数操作
//	return 0;
//}

//3.6 题⽬6
//int main()
//{
//	char* a[] = { "work","at","alibaba" };//指针数组，存放char*类型的数组
//										 //第一个char* --> w		(首元素是存放w地址的char*指针)
//										 //第二个char* --> a(at的a)
//										 //第三个char* --> a(alibaba的第一个a)
//	char** pa = a;//pa里存放了指向w地址的char*指针的地址，所以pa是一个二维指针
//	pa++;
//	printf("%s\n", *pa);
//	printf("%s\n", *(a+1));
//	return 0;
//}


//3.7 题⽬7
//int main()
//{
//	char* c[] = { "ENTER","NEW","POINT","FIRST" };
//	char** cp[] = { c + 3,c + 2,c + 1,c };
//	char*** cpp = cp;
//	printf("%s\n", **++cpp);
//	//1. ++cpp:c+2的地址
//	//2. *(++cpp):c+2('P'的地址)
//	//3. **(++cpp):'P'的地址
//
//	printf("%s\n", *-- * ++cpp + 3);
//	//1.++cpp:c+1的地址
//	//2.*(++cpp):c+1
//	//3.--(*(++cpp)):c+1自减1得到c('E地址'的地址)
//	//4.*(--(*(++cpp)):'E地址'的地址
//	//5.*(--(*(++cpp))+3:'E地址'的地址+3得到第二个'E地址'的地址
//
//	printf("%s\n", *cpp[-2] + 3);
//	//1.cpp[-2]==*(cpp-2):对c+3的地址进行解引用得到c+3('F地址'的地址)
//	//2.*cpp[-2]:'F地址'
//	//3.*cpp[-2] + 3:'S地址'
//
//	printf("%s\n", cpp[-1][-1] + 1);
//	//1.cpp[-1]==*(cpp-1):对c+2的地址解引用得到c+2
//	//2.c+2[-1]==*(c+2-1)==*(c+1):c+1是'N地址'的地址，解引用后得到'N地址'
//	//3.'N地址'+1 == 'E地址'
//	return 0;
//}

//第16讲：字符函数和字符串函数
//1. 字符分类函数
//C语⾔中有⼀系列的函数是专⻔做字符分类的，也就是⼀个字符是属于什么类型的字符的。
//这些函数的使⽤都需要包含⼀个头⽂件是 ctype.h
//这些函数的使⽤⽅法⾮常类似，我们就讲解⼀个函数的事情，其他的⾮常类似：
//int islower ( int c );//是小写字母就返回非0,不是小写字母就返回0
//int main()
//{
//	int ret1 = islower('A');
//	int ret2 = islower('a');
//	printf("%d\n", ret1);
//	printf("%d\n", ret2);
//	return 0;
//}

//练习:
//写⼀个代码，将字符串中的⼩写字⺟转⼤写，其他字符不变。
//1.正常方法:
//int main()
//{
//	char arr[] = "I Am a Student";
//	int i = 0;
//	while (arr[i] != '\0')
//	{
//		if (arr[i] >= 'a' && arr[i] <= 'z')
//		{
//			arr[i] -= 32;
//		}
//		i++;
//	}
//	printf("%s\n", arr);
//	return 0;
//}

//2.如果用islower
//int main()
//{
//	char arr[] = "I Am a Student";
//	int i = 0;
//	while (arr[i] != '\0')
//	{
//		if (islower(arr[i]))
//		{
//			arr[i] -= 32;
//		}
//		i++;
//	}
//	printf("%s\n", arr);
//	return 0;
//}


//2. 字符转换函数
//int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 
//int toupper(int c); //将参数传进去的⼩写字⺟转⼤写

//3. strlen 的使⽤和模拟实现
//size_t strlen ( const char * str );
//字符串以 '\0' 作为结束标志，strlen函数返回的是在字符串中 '\0' 前⾯出现的字符个数（不包含 '\0' )。
//参数指向的字符串必须要以 '\0' 结束。
//注意函数的返回值为 size_t，是⽆符号的（ 易错 ）
//1.计数器版本:
//size_t my_strlen(const char* p)
//{
//	size_t count = 0;
//	while (*p != '\0')
//	{
//		count++;
//		p++;
//	}
//	return count;
//}
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zu", len);
//	return 0;
//}

//2.递归版本(不创建临时变量):
//size_t my_strlen(const char* p)
//{
//	if (*p == '\0')
//		return 0;
//	else
//		return 1 + my_strlen(p + 1);
//
//}
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zu", len);
//	return 0;
//}

//3.指针-指针版本:
//size_t my_strlen(const char* p)
//{
//	const char* s = p;
//	while (*p != '\0')
//	{
//		p++;
//	}
//	return p - s;
//}
//int main()
//{
//	char arr[] = "abcdef";
//	size_t len = my_strlen(arr);
//	printf("%zu", len);
//	return 0;
//}


//4. strcpy 的使⽤和模拟实现
//char* strcpy(char * destination, const char * source ); 把source资源里的内容拷贝到destination目的地
//strcpy -- string copy 字符串拷贝
//1.源字符串必须以 '\0' 结束。
//2.会将源字符串中的 '\0' 拷⻉到⽬标空间。
//3.⽬标空间必须⾜够⼤，以确保能存放源字符串。
//4.⽬标空间必须可修改。 如果是char*p="xxxxxxxxx",p指向的是常量字符串，不能被修改，而数组里的字符串可以修改
//int main()
//{
//	char arr1[] = "abcdef";
//	char arr2[20] = { 0 };
//	strcpy(arr2, arr1);
//	printf("%s", arr2);
//	return 0;
//}

//strcpy的模拟实现:
// 1.普通版本
//void my_strcpy(char* p1,const char* p2)
//{
//	assert(p1 != NULL);
//	assert(p2 != NULL);
//	while (*p2 != '\0')
//	{
//		*p1 = *p2;
//		p1++;
//		p2++;
//	}
//	*p1=*p2;
//}

//2.另外一个好版本:
//char* my_strcpy(char* p1, const char* p2)
//{
//	assert(p1 != NULL);
//	assert(p2 != NULL);
//	char* ret = p1;
//	while (*p1++=*p2++)//后置++ 先进行表达式的运算再自增1
//	{
//		;
//	}
//	return ret;
//}
//int main()
//{
//	char arr1[] = "abcdef";
//	char arr2[20] = "xxxxxxxxxxxxxxxxxx";
//	my_strcpy(arr2, arr1);
//	printf("%s\n", arr2);
//	printf("%s\n", my_strcpy(arr2, arr1));
//	return 0;
//}


//5. strcat 的使⽤
//char * strcat ( char * destination, const char * source );
//用来连接字符串
// • 源字符串必须以 '\0' 结束。
//• ⽬标字符串中也得有 \0 ，否则没办法知道追加从哪⾥开始。
//• ⽬标空间必须有⾜够的⼤，能容纳下源字符串的内容。
//• ⽬标空间必须可修改。
//int main()
//{
//	char arr1[20] = "hello ";
//	char arr2[] = "world";
//	strcat(arr1, arr2);
//	printf("%s", arr1);
//	return 0;
//}

//strcat的模拟实现
//char* my_strcat(char* p1, const char* p2)
//{
//	assert(p1 != NULL);
//	assert(p2 != NULL);
//	char* ret = p1;
//	//或者: assert(p1&&p2)
//	while (*p1 != '\0')
//		p1++;
//	while (*p1++ = *p2++)
//		;
//	return ret;
//}
//int main()
//{
//	char arr1[20] = "hello ";
//	char arr2[] = "world";
//	my_strcat(arr1, arr2);
//	printf("%s\n", arr1);
//	//这样写也可以: 
//	//printf("%s\n", my_strcat(arr1, arr2));
//	return 0;
//}